import {withLock} from "easylock";
import {Types} from "mongoose";
import ms = require("ms");
import * as request from 'superagent'
import {InstanceType} from "typegoose"
import {ConsumeRecord, ConsumeRecordModel} from "../../database/models/consumeRecord";
import {GameActivity, GameActivityModel} from "../../database/models/gameActivity";
import {GamePrizeModel} from "../../database/models/gamePrize";
import GM from "../../database/models/gm";
import {
  LipstickSeries,
  LipstickSeriesModel,
  PlayerSeriesIncomeModel,
  SeriesIncome,
  SeriesIncomeModel
} from "../../database/models/lipstickSeries";
import {GiftState, MailModel, MailState, MailType} from "../../database/models/mail";
import {Player, PlayerModel} from "../../database/models/player";
import {Product, ProductModel, RMB_2_COIN_RATE} from "../../database/models/Product";
import {deleteProductStockSafely, getAfterReduceMostStockProductBySeries} from "../../database/models/ProductGroup";
import {RechargeWithOtherGameOrderModel} from "../../database/models/rechargeWithOtherGameOrder";
import {DEFAULT_DAPAN_THRESHOLD_RATE, SeriesIncomeThresholdModel} from "../../database/models/seriesIncomeThreshold";
import {ErrorCode, InternalError} from "../error";
import {CLIENT_TYPE, FANGKA_2_COIN, pcUrl, putDataInBroadcastInfo} from '../utils'

export const LIPSTICK_GAME_TIME_ALL: number = 129
export const LIPSTICK_GAME_REVIVE_TIME_UP: number = 600
export const LIPSTICK_GAME_REVIVE_TIME_0: number = 126
export const LIPSTICK_GAME_REVIVE_TIME_1: number = 93
export const LIPSTICK_GAME_REVIVE_TIME_2: number = 60

export async function getLittleGift(player: InstanceType<Player>) {
  try {
    const gift = {
      ok: false,
      data: null,
      msg: null
    }
    const products: Array<InstanceType<Product>> = await ProductModel.aggregate([{
      $match: {category: `littleGift`, state: 'on'}
    }/*, {$sample: {size: 1}}*/])
    if (!products) {
      console.log(`no enough gift`)
      return gift
    }

    products.sort((a, b) => {
      return b.stock - a.stock
    })

    if (!products[0] || products[0].stock <= 0) {
      console.log(`no enough gift`)
      return gift
    }

    const {ok, successList} = await deleteProductStockSafely([products[0]._id])
    if (!ok) {
      console.log(`no enough gift`)
      return gift
    }

    const product = await ProductModel.findById(successList[0])
    if (!product) {
      console.log(`no enough gift`)
      return gift
    }
    const prize = await GamePrizeModel.create({
      player: player._id,
      product: product._id,
      productName: product.name,
      productModel: product.model,
      productDescription: product.description,
      productUrl: product.coverUrl,
      inviteBy: player.inviteBy,
      state: 'idle',
      createAt: new Date(),
      from: 'freeGift',
      selectState: `notAllowed`
    })

    await MailModel.create({
      title: `小奖品发放通知`,
      content: `${prize.productName}已经添加到您的奖品中`,
      to: prize.player,
      type: MailType.NOTICE,
      gift: {prize: prize._id},
      giftState: GiftState.NOTALLOW,
      state: MailState.UNREAD
    })

    gift.ok = true
    gift.data = product
    return gift
  } catch (e) {
    console.log(e)
  }

}

export async function getDaPan(series) {
  const thres = await SeriesIncomeThresholdModel.findOne({lipstickSeries: series._id})
  const rate = thres.dapanThresholdCoinRate || DEFAULT_DAPAN_THRESHOLD_RATE
  return series.seriesPrice * rate * RMB_2_COIN_RATE
}

export class LipStickGame {

  constructor(readonly player: InstanceType<Player>,
              readonly lipstickSeries: InstanceType<LipstickSeries>,
              readonly clientType: string = CLIENT_TYPE.NORMAL) {

  }

  private async refundFangKa() {
    const {player, lipstickSeries, clientType} = this

    const re = await request.post(pcUrl + `/provideForOtherGame/refund`)
      .send({
        gem: lipstickSeries.priceInFangka,
        wechatUnionid: player.wechatUnionid,
      })
      .then(r => {
        if (r && r.body && r.body.ok) {
          return r.body
        } else {
          return {ok: false}
        }
      }).catch(e => {
        console.error(pcUrl + '/provideForOtherGame/refund ERROR', e)
        return {ok: false}
      })
  }

  private async seriesIncomeThreshold() { // 大盘
    const {lipstickSeries} = this
    // const thres = await SeriesIncomeThresholdModel.findOne({lipstickSeries: lipstickSeries._id})
    // return lipstickSeries.seriesPrice * thres.dapanThresholdCoinRate * RMB_2_COIN_RATE
    return await getDaPan(lipstickSeries)
  }

  private async reduceSeriesIncome() {
    const {lipstickSeries} = this

    await SeriesIncomeModel.update({
      lipstickSeries: lipstickSeries._id,
    }, {
      $inc: {income: -await this.seriesIncomeThreshold()},
    }, {upsert: true, new: true})
  }

  private async addBackSeriesIncome() {
    const {lipstickSeries} = this

    await SeriesIncomeModel.update({
      lipstickSeries: lipstickSeries._id,
    }, {
      $inc: {income: await this.seriesIncomeThreshold()},
    }, {upsert: true, new: true})
  }

  async start() {

    const {player, lipstickSeries, clientType} = this

    return await withLock(player.id, async () => {

      return await withLock(lipstickSeries.id, async () => {
          // if (!await detectStockOfLips(lipstickSeries.id))
          //   throw InternalError("NO_ON_STOCK")
          const price = clientType === CLIENT_TYPE.NORMAL ?
            lipstickSeries.priceInCoin : clientType === CLIENT_TYPE.PUCHENG ?
              lipstickSeries.priceInFangka : 9999999

          const {ok, consume} = await playerUseCoinOrGemOrPoint(
            player, price, lipstickSeries._id.toString(), 'lipstick', clientType)
          if (!ok)
            throw InternalError('NO_ENOUGH_COIN')

          const {ok: okay, data} = await getAfterReduceMostStockProductBySeries(lipstickSeries.id)
          if (!okay || !data.productId) {
            await ConsumeRecordModel.findByIdAndUpdate(consume, {$set: {success: false}})
            if (clientType === CLIENT_TYPE.NORMAL)
              await PlayerModel.findByIdAndUpdate(player.id, {$inc: {coin: consume.coin, freeCoin: consume.freeCoin}})
            else if (clientType === CLIENT_TYPE.PUCHENG)
              await this.refundFangKa()
            await LipstickSeriesModel.findByIdAndUpdate(lipstickSeries.id, {$set: {onStock: false}})
            throw InternalError(data.message || "NO_SUCH_PRODUCT")
          }
          const preProduct = await ProductModel.findById(data.productId)

          const income = clientType === CLIENT_TYPE.NORMAL ?
            consume.coin : clientType === CLIENT_TYPE.PUCHENG ?
              consume.fangKa * FANGKA_2_COIN : 0
          const seriesIncome: InstanceType<SeriesIncome> = await SeriesIncomeModel.findOneAndUpdate({
            lipstickSeries: lipstickSeries._id,
          }, {
            $inc: {income},
            $set: {updateAt: new Date()}
          }, {upsert: true, new: true})

          await PlayerSeriesIncomeModel.update({
            player: player._id,
            lipstickSeries: lipstickSeries._id,
          }, {$inc: {income}, $set: {updateAt: new Date()}}, {upsert: true})

          await LipstickSeriesModel.findByIdAndUpdate(lipstickSeries._id, {$inc: {hotNum: 1}})

          const win = seriesIncome.income > await this.seriesIncomeThreshold()
          const canWin = win ? Math.random() > 0.5 : false

          if (canWin)
            await this.reduceSeriesIncome()

          const activity = await GameActivityModel.create({
            player: player.id,
            lipstickSeries: lipstickSeries.id,
            canWin,
            preProduct,
            feeInFen: lipstickSeries.priceInCoin * 100,
            endAt: new Date(Date.now() + ms(`${LIPSTICK_GAME_TIME_ALL}s`)),
            from: `lipstick`,
            stage: 0
          })
          return activity
        }
      )
    })
  }

  async resume(activity: InstanceType<GameActivity>):
    Promise<{
      ok: boolean, data:
        {
          canWin: boolean,
          activity: InstanceType<GameActivity>,
          message: ErrorCode
        }
    }> {
    const {player, lipstickSeries, clientType} = this

    return await withLock(player.id, async () => {

      return await withLock(lipstickSeries.id, async () => {

        try {
          const price = clientType === CLIENT_TYPE.NORMAL ?
            lipstickSeries.priceInCoin : clientType === CLIENT_TYPE.PUCHENG ?
              lipstickSeries.priceInFangka : 9999999

          const {ok, consume} = await playerUseCoinOrGemOrPoint(player,
            price,
            lipstickSeries._id.toString(),
            `resumeActivity`, clientType)

          if (!ok)
            throw InternalError('NO_ENOUGH_COIN')

          const act = await GameActivityModel.findById(activity)
          if (!act)
            throw InternalError("ALREADY_CLAIMED")

          await this.addBackSeriesIncome()

          const income = clientType === CLIENT_TYPE.NORMAL ?
            consume.coin : clientType === CLIENT_TYPE.PUCHENG ?
              consume.fangKa * FANGKA_2_COIN : 0
          const seriesIncome: InstanceType<SeriesIncome> = await SeriesIncomeModel.findOneAndUpdate({
            lipstickSeries: lipstickSeries._id,
          }, {
            $inc: {income},
            $set: {updateAt: new Date()}
          }, {upsert: true, new: true})

          await PlayerSeriesIncomeModel.update({
            player: player._id,
            lipstickSeries: lipstickSeries._id,
          }, {$inc: {income}, $set: {updateAt: new Date()}}, {upsert: true})

          await LipstickSeriesModel.findByIdAndUpdate(lipstickSeries._id, {$inc: {hotNum: 1}})

          const win = seriesIncome.income > await this.seriesIncomeThreshold()
          const canWin = win ? Math.random() > 0.5 : false

          if (canWin)
            await this.reduceSeriesIncome()

          act.canWin = canWin

          let endAt = null
          switch (act.stage) {
            case 0:
              endAt = new Date(Date.now() + ms(`${LIPSTICK_GAME_REVIVE_TIME_0}s`))
              break
            case 1:
              endAt = new Date(Date.now() + ms(`${LIPSTICK_GAME_REVIVE_TIME_1}s`))
              break
            case 2:
              endAt = new Date(Date.now() + ms(`${LIPSTICK_GAME_REVIVE_TIME_2}s`))
              break
            default:
              endAt = new Date(Date.now() + ms(`1s`))
          }
          act.state = `free`
          act.endAt = endAt
          act.resumeTimes++
          await act.save()

          return {ok: true, data: {canWin, activity: act, message: null}}

        } catch (e) {
          return {
            ok: false,
            data: {
              canWin: null, activity: null,
              message: e.message
            }
          }
        }
      })
    })
  }

  async claim(activity: InstanceType<GameActivity>, win: boolean, stage: number):
    Promise<{
      ok: boolean, data:
        {
          win: boolean,
          activity: InstanceType<GameActivity>,
          littleGift: object,
          message: ErrorCode
        }
    }> {

    try {
      return await withLock(this.player.id, async () => {

        const {lipstickSeries, player} = this

        const act = await GameActivityModel.findById(activity.id)

        if (act.state === 'done')
          throw InternalError('ALREADY_CLAIMED')

        if (activity.canWin && win) {
          const p = await ProductModel.findById(act.preProduct)
          if (!p)
            throw InternalError("NO_SUCH_PRODUCT")

          const prize = await GamePrizeModel.create({
            player: player._id,
            lipstickSeries: lipstickSeries._id,
            product: p._id,
            productName: p.name,
            productModel: p.model,
            productDescription: p.description,
            productUrl: p.coverUrl,
            inviteBy: player.inviteBy,
            state: 'idle',
            createAt: new Date(),
            from: 'lipstick',
            selectState: `waitSelect`
          })
          const broadcastInfo = {
            name: player.name,
            from: '口红机',
            info: `奖品为：${prize.productName}`,
            createAt: new Date(Date.now())
          }
          putDataInBroadcastInfo(broadcastInfo)
          await MailModel.create({
            title: `口红机游戏胜利`,
            content: `${prize.productName}已经添加到您的奖品中`,
            to: prize.player,
            type: MailType.NOTICE,
            gift: {prize: prize._id},
            giftState: GiftState.NOTALLOW,
            state: MailState.UNREAD
          })
          await PlayerSeriesIncomeModel.update({
            player: player._id,
            lipstickSeries: lipstickSeries._id
          }, {$set: {income: 0}}, {upsert: false})
        } else
          await ProductModel.findByIdAndUpdate(act.preProduct, {$inc: {stock: 1}})

        activity.state = 'done'
        activity.stage = stage
        activity.realWin = win
        await activity.save()

        if (activity.canWin && !win)
          await this.addBackSeriesIncome()

        let littleGift
        if (!win) {
          const broadcastInfo = {
            name: player.name,
            from: '口红机',
            info: `游戏失败`,
            createAt: new Date(Date.now())
          }
          putDataInBroadcastInfo(broadcastInfo)
          littleGift = await getLittleGift(player)
        }

        await detectStockOfLips(lipstickSeries.id)
        return {ok: true, data: {win, activity, littleGift}}
      })

    } catch (e) {
      return {
        ok: false,
        data: {
          win: null, activity: null, littleGift: null,
          message: e.message
        }
      }
    }
  }
}

// 库存不够提前下架
export async function detectStockOfLips(id: Types.ObjectId): Promise<boolean> {
  const lipsAfter = await LipstickSeriesModel.findById(id).populate(`products`)
  if (!lipsAfter || lipsAfter.products.length === 0)
    return false

  for (const p of lipsAfter.products as Array<InstanceType<Product>>) {
    if (p.stock > 0 && p.state !== `off`) {
      return true
    }
  }

  await LipstickSeriesModel.findByIdAndUpdate(id, {$set: {onStock: false}})
  return false
}

export async function playerUseCoinOrGemOrPoint(
  player: InstanceType<Player>, coinOrGemOrPoint: number, objectId: Types.ObjectId, payFor: string, clientType = CLIENT_TYPE.NORMAL):
  Promise<{ ok: boolean, consume: InstanceType<ConsumeRecord> }> {
  return await withLock(player.id, async () => {
    try {
      const result = {
        ok: false,
        cost: null,
        freeCost: null,
        gem: null,
        point: null,
        fangka: null,
        consume: null
      }

      const consume = {
        player: player.id,
        objectId,
        payFor,
        success: true
      }
      const p: InstanceType<Player> =
        await PlayerModel.findById(player.id).lean()
      if (!p)
        return InternalError("NO_SUCH_USER")

      if (p.inviteBy) {
        const gm = await GM.findById(p.inviteBy).populate('relation').lean()
        if (gm && gm.relation.length > 0) {
          const gmIds = []
          for (const it of gm.relation) {
            gmIds.push(it._id)
          }
          consume['relation'] = gmIds
        }
      }

      if (payFor === 'lipstick' || payFor === 'resumeActivity' || payFor === 'pressAccurate') {
        if (clientType === CLIENT_TYPE.NORMAL) {
          const coin = coinOrGemOrPoint
          if (p.coin + p.freeCoin < coin)
            return result

          let cost
          let freeCost
          if (coin < p.freeCoin) {
            cost = 0
            freeCost = coin
          } else {
            cost = coin - p.freeCoin
            freeCost = p.freeCoin
          }
          await PlayerModel.findByIdAndUpdate(player.id, {$inc: {coin: -cost, freeCoin: -freeCost}})

          const cm = await ConsumeRecordModel.create({
            ...consume,
            coin: cost,
            freeCoin: freeCost,
            gem: 0,
            point: 0
          })

          result.ok = true
          result.consume = cm
          return result
        } else if (clientType === CLIENT_TYPE.PUCHENG) {
          const fangKa = coinOrGemOrPoint
          const rechargeOrder = await RechargeWithOtherGameOrderModel.create({
            player: p.id,
            otherMoney: fangKa,
            coin: 0,
            freeCoin: 0,
            gem: 0,
            type: 'consume',
            state: 'unpaid',
            gameType: payFor,
            createAt: new Date()
          })

          const res = await request.post(pcUrl + `/provideForOtherGame/recharge`)
            .send({
              rechargeOrderId: rechargeOrder._id,
              gem: rechargeOrder.otherMoney,
              type: rechargeOrder.type,
              wechatUnionid: player.wechatUnionid,
              gameType: rechargeOrder.gameType
            })
            .then(r => {
              if (r && r.body && r.body.ok) {
                return r.body
              } else {
                return {ok: false}
              }
            }).catch(e => {
              console.error(pcUrl + '/provideForOtherGame/recharge ERROR', e)
              return {ok: false}
            })
          if (!res || !res.ok)
            return result

          rechargeOrder.finishAt = new Date()
          rechargeOrder.state = 'paid'
          await rechargeOrder.save()

          const cm = await ConsumeRecordModel.create({
            ...consume,
            coin: 0,
            freeCoin: 0,
            gem: 0,
            point: 0,
            fangKa
          })

          result.ok = true
          result.consume = cm
          return result
        } else {
          console.error(`Unknow Client type `, clientType)
        }
      } else if (payFor === 'randomLuckBox' || payFor === 'rotateEgg') {
        const gem = coinOrGemOrPoint
        if (p.gem < gem)
          return result

        await PlayerModel.findByIdAndUpdate(player.id, {$inc: {gem: -gem, point: gem}})
        const cm = await ConsumeRecordModel.create({
          ...consume,
          coin: 0,
          freeCoin: 0,
          gem,
          point: 0
        })

        result.ok = true
        result.consume = cm
        return result
      } else if (payFor === 'pointMarket') {
        const point = coinOrGemOrPoint
        if (p.point < point)
          return result

        await PlayerModel.findByIdAndUpdate(player.id, {$inc: {point: -point}})
        const cm = await ConsumeRecordModel.create({
          ...consume,
          coin: 0,
          freeCoin: 0,
          gem: 0,
          point
        })

        result.ok = true
        result.consume = cm
        return result
      }
    } catch (e) {
      console.log(e)
    }
  })
}
